#define _FNU_SOURCE
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<assert.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<sys/epoll.h>
#include<fcntl.h>
#include<errno.h>

#define MAX_FD 128
#define DATALEN 1024
#define EPOLLSIZE 5
#define LT 0
#define ET 1

//初始化服务器端套接字
int InitSocket()
{
    int sockfd=socket(AF_INET,SOCK_STREAM,0);
    if(sockfd==-1)return -1;

    struct sockaddr_in saddr;
    memset(&saddr,0,sizeof(saddr));
    saddr.sin_family=AF_INET;
    saddr.sin_port=htons(2222);
    saddr.sin_addr.s_addr=INADDR_ANY;

    int res=bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
    if(res==-1)
    {
        printf("bind errno\n");
        return -1;
    }


    res=listen(sockfd,5);
    if(res==-1)
    {
        printf("listen errno\n");
        return -1;
    }

    return sockfd;
}

//设置文件为非阻塞模式
void SetNoWait(int fd)
{
    int old_option=fcntl(fd,F_GETFL);//fcntl系统调用可以用来对已打开的文件描述符进行各种控制操作以改变已打开文件的的各种属性
    int new_option=old_option | O_NONBLOCK;
    fcntl(fd,F_SETFL,new_option);
}

//关闭客户端连接
void CloseClient(int epfd,int fd)
{
    close(fd);
    printf("A Clien is close\n");
    if(epoll_ctl(epfd,EPOLL_CTL_DEL,fd,NULL)==-1)
    {
        printf("epoll_ctl del error");
    }
}

//获取一个新到客户端连接，如果flag为ET，则以ET模式处理
void GetClientLink(int sockfd,int epfd,int flag)
{
    struct sockaddr_in caddr;
    socklen_t len=sizeof(caddr);
    int c =accept(sockfd,(struct sockaddr*)&caddr,&len);
    if(c<0)
    {
        printf("Client Link error\n");
        return;
    }
    struct epoll_event ev;
    ev.data.fd=c;
    if(flag)
    {
        ev.events=EPOLLIN | EPOLLRDHUP | EPOLLET;
        SetNoWait(c);
    }
    else
    {
        ev.events=EPOLLIN | EPOLLRDHUP;
    }
    if(epoll_ctl(epfd,EPOLL_CTL_ADD,c,&ev)==-1)
    {
        printf("epoll_ctl add error\n");
    }
}

//LT模式到客户端数据处理方式
void LTDealClientData(int epfd,int fd)
{
    char buff[DATALEN]={0};
    int n=recv(fd,buff,DATALEN-1,0);
    if(n<=0)
    {
        CloseClient(epfd,fd);
        return;
    }
    printf("%d:%s\n",fd,buff);
    send(fd,"OK",2,0);
}

//ET模式到客户端数据处理方式
void ETDealClientData(int epfd,int fd)
{
    while(1)
    {
        char buff[DATALEN]={0};
        int n= recv(fd,buff,DATALEN-1,0);
        if(n==-1)
        {
            if(errno==EAGAIN || errno==EWOULDBLOCK)//api函数发生异常时,一般会将errno变量赋一个整数值,不同的值表示不同的含义,可以通过查看该值推测出错的原因
            {
                printf("read later\n");
                break;
            }
            else
            {
                CloseClient(epfd,fd);
                break;
            }
        }
        else if(n==0)
        {
            CloseClient(epfd,fd);
            break;
        }
        else
        {
            printf("%d:%s\n",fd,buff);
            send(fd,"OK",2,0);
        }
    }
}

//处理就绪的文件描述符上的数据
void DealReadyEvent(struct epoll_event *events,int n,int sockfd,int epfd)
{
    int i=0;
    for(;i<n;i++)
    {
        int fd=events[i].data.fd;
        if(fd==sockfd)
        {
            GetClientLink(sockfd,epfd,LT);//设置LT  or  ET  模式
        }
        else if(events[i].events & EPOLLRDHUP)//EPOLLRDHUP事件 判断对端是否关闭（当socket接收到对方关闭连接时的请求之后触发，有可能是TCP连接被对方关闭，也有可能是对方关闭了写操作。）
        {
            CloseClient(epfd,fd);
        }
        else if(events[i].events & EPOLLIN)//EPOLLIN事件，连接到达；有数据来临；
        {
            LTDealClientData(epfd,fd);
            //ETDealClientData(epfd,fd);
        }
        else
        {
            printf("error\n");
        }
    }
}


int main()
{
    int sockfd=InitSocket();
    assert(sockfd!=-1);

    int epfd=epoll_create(EPOLLSIZE);//创建内核事件表
    assert(epfd!=-1);

    struct epoll_event ev;
    ev.data.fd=sockfd;
    ev.events=EPOLLIN;

    if(epoll_ctl(epfd,EPOLL_CTL_ADD,sockfd,&ev)==-1)//往内核事件表中注册fd上的事件
    {
        printf("epoll_ctl add error\n");
        exit(0);
    }

    while(1)
    {
        struct epoll_event events[MAX_FD];
        int n=epoll_wait(epfd,events,MAX_FD,-1);//一段超时事件内等待一组文件描述符上的事件
        if(n<0)
        {
            printf("epoll_wait error\n");
            continue;
        }
        else if(n==0)
        {
            printf("已超时\n");
            continue;
        }
        else
        {
            DealReadyEvent(events,n,sockfd,epfd);
        }
    }
}

